/**
 * Mock Request Router
 * @author Wangtd
 */
const express = require('express');
const router = express.Router();
const os = require('os');
const uuid = require('uuid');
const fs = require('fs');
const path = require('path');
const httpRequest = require('request');
const multipart = require('connect-multiparty');
const util = require('./util.js');
const logger = require('./logger.js');
const config = require('../config.js');

router.post('/module/get', function(request, response) {
    var filedir = config.mockDir;
    var files = fs.readdirSync(filedir);
    var modules = [];
    for (var i in files) {
        var filename = files[i];
        var file = fs.statSync(filedir + '/' + filename);
        if(file.isDirectory()) {
            modules.push(filename);
        }
    }
    response.json(modules);
});

router.post('/module/save', function(request, response) {
    var filepath = path.join(config.mockDir, request.body.module);
    logger.info(request, 'Create module: %s', filepath);
    if(!fs.existsSync(filepath)) {
        fs.mkdirSync(filepath);
    }
    response.json(true);
});

router.post('/module/rename', function(request, response) {
    var params = request.body;
    var oldPath = path.join(config.mockDir, params.oldModule),
        newPath = path.join(config.mockDir, params.newModule);
    logger.info(request, 'Rename module: %s=>%s', oldPath, newPath);
    if(fs.existsSync(newPath)) {
        response.json(false);
    } else {
        fs.renameSync(oldPath, newPath);
        response.json(true);
    }
});

router.post('/module/delete', function(request, response) {
    var filepath = path.join(config.mockDir, request.body.module);
    logger.info(request, 'Delete module: %s', filepath);
    util.deleteAllSync(filepath);
    response.json(true);
});

router.post('/data/tree', function(request, response) {
    var params = request.body;
    var nodes = {
        name: 'MOCK',
        open: true,
        root: true,
        leaf: false
    };
    readDirSync(path.join(config.mockDir, params.module), nodes);
    response.json(nodes);
});

router.post('/data/save', function(request, response) {
    var params = request.body;
    if (params.type == 'folder') {
        var filepath = path.join(config.mockDir, params.module, params.folder);
        logger.info(request, 'Create directory: %s', filepath);
        if(fs.existsSync(filepath)) {
            response.json(false);
        } else {
            fs.mkdirSync(filepath);
            response.json(true);
        }
    } else if (params.type == 'file') {
        var filepath = path.join(config.mockDir, params.module, params.folder + '/' + params.file);
        logger.info(request, 'Update file: path=%s, size=%d', filepath, params.data.length);
        if(params.created && fs.existsSync(filepath)) {
            response.json(false);
        } else {
            var modified = null;
            if (fs.existsSync(filepath)) {
                modified = fs.statSync(filepath).mtime.getTime();
                if (modified != params.modified) {
                    return response.end('expired');
                }
            }
            util.writeFileSync(filepath, params.data, true);
            // update modified
            modified = fs.statSync(filepath).mtime.getTime();
            response.json(modified);
        }
    }
});

router.post('/data/rename', function(request, response) {
    var params = request.body;
    var oldPath = path.join(config.mockDir, params.module, params.oldPath),
        newPath = path.join(config.mockDir, params.module, params.newPath);
    logger.info(request, 'Rename path: %s=>%s', oldPath, newPath);
    if(fs.existsSync(newPath)) {
        response.json(false);
    } else {
        fs.renameSync(oldPath, newPath);
        response.json(true);
    }
});

router.post('/data/delete', function(request, response) {
    var params = request.body;
    var filepath = path.join(config.mockDir, params.module, params.path);
    logger.info(request, 'Delete path: %s', filepath);
    util.deleteAllSync(filepath);
    response.json(true);
});

router.post('/data/copy', function(request, response) {
    var params = request.body;
    var source = path.join(config.mockDir, params.module, params.source);
    var target = path.join(config.mockDir, params.module, params.target);
    // copy
    if (params.type == 'folder') {
        logger.info(request, 'Copy directory: %s=>%s', source, target);
        params.ignore.root = true;
        copyFolder(source, target, params.ignore);
    } else if (params.type == 'file') {
        logger.info(request, 'Copy file: %s=>%s', source, target);
        var filepath = path.dirname(target);
        if(!fs.existsSync(filepath)) {
            util.mkdirsSync(filepath);
        }
        copyFile(source, target, params.ignore);
    }
    // delete
    if (params.movable) {
        logger.info(request, 'Delete path: %s', source);
        util.deleteAllSync(source);
    }
    response.json(true);
});

router.post('/tool/read', multipart(), function(request, response) {
    var content = '';
    if (request.files) {
        var file = request.files['file'];
        content = util.readFileSync(file.path);
        // delete file
        fs.unlinkSync(file.path);
    }
    response.writeHead(200, {
        'content-type': 'text/plain;charset=utf-8'
    });
    response.end(content);
});

router.post('/tool/write', function(request, response) {
    var filename = request.body.filename,
        filepath = path.join(os.tmpdir(), uuid.v1() + path.extname(filename));
    // save file
    util.writeFileSync(filepath, request.body.content, true);
    response.writeHead(200, {
        'content-type': 'application/octet-stream',
        'content-disposition': 'attachment;filename=' + encodeURI(filename)
    });
    // response stream
    fs.createReadStream(filepath, 'utf-8').on('end', function() {
        fs.unlinkSync(filepath);
    }).pipe(response);
});

router.post('/blog/upload', multipart(), function(request, response) {
    var filename = '';
    if (request.files) {
        var file = request.files['file'];
        filename = uuid.v1() + path.extname(file.name);
        var filepath = path.join(config.blogDir, 'images', filename);
        // copy file
        fs.createReadStream(file.path).pipe(fs.createWriteStream(filepath));
        // delete file
        fs.unlinkSync(file.path);
    }
    response.end(filename);
});

router.post('/blog/category', function(request, response) {
    tryBlogLock(function() {
        var filepath = config.blogDir + '/blogs.json',
            blogs = JSON.parse(util.readFileSync(filepath)),
            categories = blogs.categories;
        var category = null;
        // check category
        if (util.find(categories, function(category) {
            return category.id != request.body.id && category.name == request.body.category;
        })) {
            return response.end('existed');
        }
        if (request.body.id) {
            // update
            for (var i = 0; i < categories.length; i++) {
                if (categories[i].id == request.body.id) {
                    category = categories[i];
                    categories[i].name = request.body.category;
                    break;
                }
            }
        } else {
            // insert
            category = {
                id: new Date().getTime() + '',
                name: request.body.category
            };
            categories.push(category);
        }
        // order
        if (categories.indexOf(category) != request.body.order) {
            var ordered = false, orderedCategories = [];
            for (var i = 0; i < categories.length; i++) {
                if (i == request.body.order) {
                    ordered = true;
                    orderedCategories.push(category);
                }
                if (categories[i].id != category.id) {
                    orderedCategories.push(categories[i]);
                }
            }
            if (!ordered) {
                orderedCategories.push(category);
            }
            blogs.categories = orderedCategories;
        }
        // update category
        util.writeFileSync(filepath, JSON.stringify(blogs, null, 2), true);
        // response category
        response.json(category);
    }, function() {
        response.end('locked');
    });
});

router.post('/blog/publish', function(request, response) {
    tryBlogLock(function() {
        var blogid = request.body.blogid,
            blog = null,
            category = null;
        var blogs = JSON.parse(util.readFileSync(config.blogDir + '/blogs.json'));
        // category
        if (request.body.category) {
            category = request.body.category;
        } else {
            var oCategory = util.find(blogs.categories, function(category) {
                return category.name == request.body.categoryName;
            });
            if (oCategory) {
                category = oCategory.id;
            } else {
                category = new Date().getTime() + '';
                blogs.categories.push({
                    id: category,
                    name: request.body.categoryName
                });
            }
        }
        // article
        if (blogid) {
            for (var i = 0; i < blogs.articles.length; i++) {
                if (blogs.articles[i].id == blogid) {
                    blog = blogs.articles[i];
                    if (request.body.modified !== blog.modified) {
                        return response.end('expired');
                    }
                    // update blog
                    blogs.articles[i].title = request.body.title;
                    blogs.articles[i].category = category;
                    blogs.articles[i].tags = request.body.tags;
                    blogs.articles[i].snapshot = request.body.snapshot;
                    blogs.articles[i].modified = new Date().getTime();
                    break;
                }
            }
        }
        if (blog == null) {
            var oBlog = util.find(blogs.articles, function(article) {
                return article.title == request.body.title;
            });
            if (oBlog) {
                return response.end('existed');
            } else {
                var now = new Date().getTime();
                blog = {
                    id: uuid.v1(),
                    title: request.body.title,
                    category: category,
                    tags: request.body.tags,
                    snapshot: request.body.snapshot,
                    created: now,
                    modified: now
                };
                blogs.articles.unshift(blog);
            }
        }
        logger.info(request, 'Publish blog: id=%s, size=%d', blog.id, request.body.content.length);
        // update blogs
        util.writeFileSync(config.blogDir + '/blogs.json', JSON.stringify(blogs, null, 2), true);
        // update article
        util.writeFileSync(config.blogDir + '/articles/' + blog.id + '.md', request.body.content, true);
        // response blog
        response.json(blog);
    }, function() {
        response.end('locked');
    });
});

router.post('/blog/delete', function(request, response) {
    tryBlogLock(function() {
        var blogs = JSON.parse(util.readFileSync(config.blogDir + '/blogs.json'));
        // category
        if (request.body.category) {
            // check category
            var oArticle = util.find(blogs.articles, function(article) {
                return article.category == request.body.category;
            });
            if (oArticle) {
                return response.end('used');
            }
            // delete category
            for (var i = 0; i < blogs.categories.length; i++) {
                if (blogs.categories[i].id == request.body.category) {
                    blogs.categories.splice(i, 1);
                    break;
                }
            }
        }
        // article
        if (request.body.article) {
            for (var i = 0; i < blogs.articles.length; i++) {
                if (blogs.articles[i].id == request.body.article) {
                    blogs.articles[i].disabled = true;
                    logger.info(request, 'Delete blog: ID[%s]', blogs.articles[i].id);
                    break;
                }
            }
        }
        // update blogs
        util.writeFileSync(config.blogDir + '/blogs.json', JSON.stringify(blogs, null, 2), true);
        response.json(true);
    }, function() {
        response.end('locked');
    });
});

router.post('/test/send', function(request, response) {
    var params = request.body;
    var options = {
        url: params.url,
        method: params.method,
        headers: {
            'encoding': 'UTF-8',
            'Connection': 'keep-alive',
            'Content-Type': 'application/' + params.type,
            'Cookie': request.headers.cookie
        },
        timeout: 30000 // 30s
    };
    // headers
    if (params.headers) {
        for (var name in params.headers) {
            options.headers[name] = params.headers[name];
        }
    }
    // body
    if (params.data != null) {
        options['body'] = params.data;
    }
    httpRequest(options).on('error', function(error) {
        console.log(error);
        return response.end('The server has disconnected or the connection has timed out.');
    }).pipe(response);
});

router.post('/test/request', multipart(), function(request, response) {
    var url = request.body['__url'],
        method = request.body['__method'],
        headers = JSON.parse(request.body['__headers']),
        cookies = JSON.parse(request.body['__cookies']),
        body = request.body['__body'];

    var options = {
        url: url,
        method: method,
        headers: {
            'encoding': 'UTF-8',
            'Connection': 'keep-alive',
             // cookies
            'Cookie': reformCookies(request.headers.cookie, cookies)
        },
        timeout: 60000 // 60s
    };

    // headers
    for (var name in headers) {
        options.headers[name] = headers[name];
    }

    // body
    if (method != 'GET' && method != 'HEAD') {
        switch (headers['Content-Type']) {
            case 'multipart/form-data':
                var formData = {};
                if (body) {
                    var fields = JSON.parse(body);
                    for (var name in fields) {
                        formData[name] = fields[name];
                    }
                }
                if (request.files) {
                    for (var name in request.files) {
                        var file = request.files[name];
                        formData[name] = {
                            value: fs.createReadStream(file.path),
                            options: {
                                filename: file.originalFilename,
                                contentType: file.type
                            }
                        };
                    }
                }
                options['formData'] = formData;
                break;
            case 'application/json':
                options['json'] = true;
                if (body) {
	                options['body'] = JSON.parse(body);
                }
                break;
            case 'application/x-www-form-urlencoded':
                if (body) {
	                options['form'] = JSON.parse(body);
                }
                break;
            case 'text/plain':
            case 'application/javascript':
            case 'application/xml':
            case 'text/xml':
            case 'text/html':
                options['body'] = body;
                break;
            default:
                options['body'] = body;
        }
    }

    httpRequest(options).on('error', function(error) {
        console.log(error);
        response.writeHead(504, {
            'content-type': 'text/plain;charset=utf-8'
        });
        return response.end('The server has disconnected or the connection has timed out.');
    }).on('response', function(response) {
        // set cookies
        response.headers['x-cookies'] = (response.headers['set-cookie'] || []).join('@');
    }).on('end', function() {
        // delete files
        if (request.files) {
            for (var name in request.files) {
                fs.unlinkSync(request.files[name].path);
            }
        }
    }).pipe(response);
});

function reformCookies(cookie, cookies) {
    if (Object.keys(cookies).length == 0) {
        return cookie;
    } else {
        var pattern = /([^=]+)=([^;]+);?\s*/g,
            result = null;
        while ((result = pattern.exec(cookie)) != null) {
            cookies[result[1]] = result[2];
        }
        // cookies to string
        var values = [];
        for (var name in cookies) {
            values.push(name + '=' + cookies[name]);
        }
        return values.join('; ');
    }
}

function copyFolder(source, target, ignore) {
    var copyable = ignore.root || ignore.folder;
    if (!fs.existsSync(target)) {
        copyable = true;
        util.mkdirsSync(target);
    }
    if (copyable) {
        ignore.root = false;
        var files = fs.readdirSync(source);
        for (var i in files) {
            var filename = files[i];
            var curpath = source + '/' + filename;
            var tgtpath = target + '/' + filename;
            if(fs.statSync(curpath).isDirectory()) {
                copyFolder(curpath, tgtpath, ignore);
            } else {
                copyFile(curpath, tgtpath, ignore);
            }
        }
    }
}

function copyFile(source, target, ignore) {
    if (!fs.existsSync(target) || ignore.file) {
        //fs.createReadStream(source).pipe(fs.createWriteStream(target));
        util.copyFileSync(target, source, true);
    }
}

function readDirSync(filedir, srcnode) {
    var children = [];
    var files = fs.readdirSync(filedir);
    for (var i in files) {
        var filename = files[i];
        var subnode = {};
        subnode.name = filename;
        var file = fs.statSync(filedir + '/' + filename);
        if(file.isDirectory()) {
            subnode.leaf = false;
            readDirSync(filedir + '/' + filename, subnode);
        } else {
            subnode.leaf = true;
        }
        children.push(subnode);
    }
    srcnode.children = children;
}

function tryBlogLock(resolve, reject) {
    var enabled = false,
        lockpath = config.blogDir + '/blogs.lock';
    try {
        if(fs.existsSync(lockpath)) {
            reject();
        } else {
            try {
                fs.writeFileSync(lockpath, 'locked!', { flag: 'wx' });
                enabled = true;
                resolve();
            } catch (e) {
                reject();
            }
        }
    } finally {
        if (enabled) {
            fs.unlinkSync(lockpath);
        }
    }
}

module.exports = router;
